package ch.myDungeons.characters;

import java.io.Serializable;

import ch.myDungeons.game.Inventory;
import ch.myDungeons.game.Map;
import ch.myDungeons.game.Tile;
import ch.myDungeons.game.Inventory.BagSlot;
import ch.myDungeons.gameObjects.Armor;
import ch.myDungeons.gameObjects.Item;
import ch.myDungeons.gameObjects.Potion;
import ch.myDungeons.gameObjects.Weapon;
import ch.myDungeons.io.LanguagePack;
import ch.myDungeons.game.MyDungeon;
import ch.myDungeons.view.Console;

/**
 * The character superclass. Every NPC and player inherits this class
 * @author Grii
 *
 */

public abstract class Person implements Serializable{
	private static final long serialVersionUID = 3020402206198183643L;

	/** The name of the person */
	protected String name = "NPC";

	/** The level of the person */
	private int level = 1;

	/** How many hitpoints the person can have */
	private int maxHP = 100;

	/** How many manapoints the person can have */
	private int maxMP = 100;

	/** current hitpoints, manapoints the person has */
	private int currentHP;

	private int currentMP;

	/** All the items the person has in his inventory */
	private Inventory inventory;
		
	/** Where this person is on the map */
	private int posX;

	private int posY;
	
	/** Whether this person is friendly or not */
	private boolean friendly = true;
	
	/** How the persons natural weapon is called */
	private String naturalWeapon = "bare hands";

	/** How much bonus damage this person does if he has no weapon equipped */
	private int baseAttackBonus = 1;

	/** How many dice this person can roll to calculate his damage if no weapon equipped */
	private int baseAttackDice = 0;
	
	/** How much damage the natural armor of this person absorbs */
	private int naturalDamageResistance = 0;
	
	/** The active weapon of this person */
	private Weapon activeWeapon = null;
	
	/** The active armor of this person */
	private Armor activeArmor = null;
	
	/** the map this person is on */
	private Map map;
	
	/** The Console this person prints on */
	protected transient Console console;
	
	public Person (String name, int level) {
		this.name = name;
		this.setLevel(level);
		inventory = new Inventory();
	}
	
	/** Place the person on the map */
	public boolean placeOnMap(int posX, int posY, Map map, Console console) {
		if (map.checkAccessability(posX, posY)) {
			this.setMap(map);
			this.setPosX(posX);
			this.setPosY(posY);
			this.console = console;
			map.tiles[posX][posY].personOnTheTile = this;
			return true;
		} else {
			return false;
		}
	}
	
	public void setConsole(Console console){
		this.console = console;
	}
	
	/** Executed every turn */
	protected void doTurn(){
		if (getMap() == null) {
			System.err.println("Person not placed");
		}
		if (getCurrentHP() <= 0) {
			die();
		}
	}
	
	/**
	 * Move this {@link Person} to another tile
	 * @param direction 0 for north, 1 for east, 2 for south,  3 for west, other %4
	 */
	protected boolean move(int direction){
		int targetX = getMap().getPostitionFromDirection(direction, getPosX(), getPosY())[0];
		int targetY = getMap().getPostitionFromDirection(direction, getPosX(), getPosY())[1];

		if (getMap().checkAccessability(targetX, targetY)) {
			getMap().tiles[getPosX()][getPosY()].personOnTheTile = null;
			setPosX(targetX);
			setPosY(targetY);
			getMap().tiles[getPosX()][getPosY()].personOnTheTile = this;
			console.println(getNameWithPrefix() + " " + LanguagePack.get("moves_to") + " (" + targetX + ", " + targetY + ")");
			return true;
		} else {
			return false;
		}
	}
	
	/** Attack the person next to this person. Returns false if failed */
	protected boolean attack(int targetX, int targetY){
		if (getMap().checkOccupied(targetX, targetY)) {
			Person target = getMap().tiles[targetX][targetY].personOnTheTile;
			int dmg = target.calculatereceivedDamage(calculateAttackDamage());
			String weaponName = getNaturalWeapon();
			if (getActiveWeapon() != null) {
				weaponName = getActiveWeapon().getName();
			}
			console.println(getNameWithPrefix() + " " + LanguagePack.get("attacks") + " " + target.getNameWithPrefix()
					+ " " + LanguagePack.get("with_his") + " " + weaponName + " " + LanguagePack.get("and_deals") + " " 
					+ dmg + " " + LanguagePack.get("damage_the_target_now_has") + " " + (target.getCurrentHP()-dmg) 
					+ " " + LanguagePack.get("hitpoints_left"));
			target.receiveDirectDamage(dmg);
			return true;
		} else {
			return false;
		}
	}
	
	/** Calculates how much damage this person does with his attack */
	public int calculateAttackDamage(){
		int damage = 0;
		if(getActiveWeapon() == null){
			damage = getBaseAttackDice() * ((int)(Math.random()*6) + 1) + getBaseAttackBonus();
		} else {
			damage = getActiveWeapon().getAttackDamage();
		}
		return damage;
	}
	
	/** Calculates how much damage this person receives from the attack */
	public int calculatereceivedDamage(int damage){
		int dmg = 0;
		if(getActiveArmor() == null){
			dmg = damage - getNaturalDamageResistance();
		} else {
			dmg = damage - getActiveArmor().armorBonus;
		}
		if (dmg < 0) {
			dmg = 0;
		}
		return dmg;
	}
	
	/** Picks up all items */
	public int pickUpAll(){
		int itemCount = getMap().tiles[getPosX()][getPosY()].inventory.size();
		while(!getMap().tiles[getPosX()][getPosY()].inventory.isEmpty()){
			pickUp(0);
		}
		return itemCount;
	}
	
	/** Picks up the ith item on the floor */
	public boolean pickUp(int i){
		if (i < getMap().tiles[getPosX()][getPosY()].inventory.size() && i >= 0) {
			BagSlot bagSlot = getMap().tiles[getPosX()][getPosY()].inventory.get(i);
			getMap().tiles[getPosX()][getPosY()].inventory.remove(i);
			inventory.addItem(bagSlot);
			return true;
		} else return false;
	}
	
	/** Returns the name with the friendly/evil prefix */
	public String getNameWithPrefix(){
		if (isFriendly()) {
			return LanguagePack.get("a_friendly") + " " + getName();
		} else {
			return LanguagePack.get("an_evil") + " " + getName();
		}
	}
	
	/** Reduces the currentHP of this Person by damage */
	public void receiveDirectDamage(int damage){
		setCurrentHP(getCurrentHP() - damage);
		if(getCurrentHP() <= 0){
			setCurrentHP(0);
		}
	}
	
	/** Adds a weapon to the inventory and equips it */
	public void addAndEquipWeapon(Weapon weapon){
		setActiveWeapon((Weapon)inventory.get(inventory.addItem(weapon)).getItem());
	}
	
	/** Adds an armor to the inventory and equips it */
	public void addAndEquipArmor(Armor armor){
		setActiveArmor((Armor)inventory.get(inventory.addItem(armor)).getItem());
	}
	
	/** Used to equip an item in the inventory */
	public boolean equipItem(int itemIndex){
		if (inventory.get(itemIndex).getItem() instanceof Weapon) {
			setActiveWeapon((Weapon) inventory.get(itemIndex).getItem());
		} else if (inventory.get(itemIndex).getItem() instanceof Armor) {
			setActiveArmor((Armor) inventory.get(itemIndex).getItem());
		} else return false;
		return true;
	}
	
	/** Drink a potion */
	public boolean drinkPotion(int itemIndex){
		if (inventory.get(itemIndex).getItem() instanceof Potion) {
			((Potion)inventory.get(itemIndex).getItem()).use(this);
			inventory.get(itemIndex).removeOne();
			if (inventory.get(itemIndex).getCount() <= 0) {
				inventory.remove(itemIndex);
			}
			return true;
		}
		return false;
	}
	
	/** Executed on person death */
	public void die(){
		console.println(getNameWithPrefix() + " " + LanguagePack.get("dies"));
		for (int i = 0; i < inventory.size(); i++) {
			getMap().tiles[getPosX()][getPosY()].inventory.addItem(inventory.get(i)); // Drop all items
		}
		getMap().tiles[getPosX()][getPosY()].personOnTheTile = null;
		MyDungeon.game.removePerson(this);
	}
	
	/** Drop the ith item in the inventory */
	boolean drop (int i){
		if (inventory.size() <= i || i < 0) {
			return false;
		}
		getMap().tiles[getPosX()][getPosY()].inventory.addItem(inventory.get(i));
		if(inventory.get(i).getItem() == getActiveArmor()){
			setActiveArmor(null);
		}
		if(inventory.get(i).getItem() == getActiveWeapon()){
			setActiveWeapon(null);
		}
		inventory.remove(i);
		return true;
	}
	
	/** Throw an item into a direction */
	boolean throwItem(int direction, int itemIndex) {
		if (inventory.size() <= itemIndex || itemIndex < 0) {
			return false;
		}
		
		// create a single copy of this item
		Item item = inventory.get(itemIndex).getItem().getCopy();
		
		// Remove the item from the person's inventory
		if (inventory.get(itemIndex).getCount() == 1) {
			if(inventory.get(itemIndex).getItem() == getActiveArmor()){
				setActiveArmor(null);
			}
			if(inventory.get(itemIndex).getItem() == getActiveWeapon()){
				setActiveWeapon(null);
			}
			inventory.remove(itemIndex);
		} else {
			inventory.get(itemIndex).removeOne();
		}
		
		Tile targetTile = item.getTrowItemTargetTile(direction, getPosX(), getPosY(), getMap());
		boolean hitSomething = (targetTile.personOnTheTile != null);
		
		// Potions
		if(item instanceof Potion){
			if (hitSomething) {
				((Potion)item).use(targetTile.personOnTheTile);
			} else {
				// blablabla potion broke
			}
			return true;
		}
		// Damage target if the item is a weapon
		else if (item instanceof Weapon) {
			Weapon tmpWeapon = getActiveWeapon();
			setActiveWeapon((Weapon)item);
			if (hitSomething) {
				targetTile.personOnTheTile.inventory.addItem((Weapon)item);
				attack(targetTile.posX, targetTile.posY);
			} else {
				targetTile.inventory.addItem((Weapon)item);
			}
			setActiveWeapon(tmpWeapon);
			
		} else {
			targetTile.inventory.addItem(item);
		}
		return true;
	}

	/** returns a random value between 0 and 3 */
	int getRandomDirection () {
		return (int)(Math.random()*4);
	}

	/** Returns a string containing the stats of the person */
	public String getInfo(){
		String result = "";
		result += LanguagePack.get("name") + ": " + LanguagePack.capitalizeString(LanguagePack.get(name));
		if (isFriendly()) {
			result += "\n" + LanguagePack.get("friendly");
		} else {
			result += "\n" + LanguagePack.get("evil");
		}
		result += "\n" + LanguagePack.get("level") + ": " + getLevel();
		result += "\n" + LanguagePack.get("hitpoints") + ": " + getCurrentHP() + "/" + getMaxHP();
		result += "\n" + LanguagePack.get("manapoints") + ": " + getCurrentMP() + "/" + getMaxMP();
		if (getActiveWeapon() != null) {
			result += "\n" + LanguagePack.get("weapon") + ": " + LanguagePack.capitalizeString(getActiveWeapon().getName())
					+ " (" + getActiveWeapon().attackDamageDice + "d6 + " + getActiveWeapon().attackDamageBonus + " " + LanguagePack.get("damage") + ")";
		} else {
			result += "\n" + LanguagePack.get("weapon") + ": " + LanguagePack.capitalizeString(getNaturalWeapon())
					+ " (" + getBaseAttackDice() + "d6 + " + getBaseAttackBonus() + " " + LanguagePack.get("damage") + ")";
		}
		if (getActiveArmor() != null) {
			result += "\n" + LanguagePack.capitalizeString(LanguagePack.get("armor")) + ": " + LanguagePack.capitalizeString(getActiveArmor().getName())
					+ " (+" + getActiveArmor().armorBonus + " " + LanguagePack.get("armor") + ")";
		} else {
			result += "\n" + LanguagePack.get("armor_no_armor") + " (+" + getNaturalDamageResistance() + " " + LanguagePack.get("armor") + ")";
		}
		return result;
	}

	/** Returns name, level, maxHP, maxMP, currentHP, currentMP, friendly, naturalWeapon, baseAttackBonus, baseAttackDice, naturalDamageResistance, activeWeaponIndex, activeArmorIndex, items. split at PERSONINFO */
	public String getSave(){
		int activeWeaponIndex = -1;
		int activeArmorIndex = -1;
		for (int i = 0; i < inventory.size(); i++) {
			if(inventory.get(i).getItem() == getActiveArmor()){
				activeArmorIndex = i;
			}
			if(inventory.get(i).getItem() == getActiveWeapon()){
				activeWeaponIndex = i;
			}
		}
		String result = name + "<PERSONINFO>" + getLevel() + "<PERSONINFO>" + getMaxHP() + "<PERSONINFO>" + getMaxMP() + "<PERSONINFO>" +
				getCurrentHP() + "<PERSONINFO>" + getCurrentMP() + "<PERSONINFO>" + isFriendly() + "<PERSONINFO>" + getNaturalWeapon() + "<PERSONINFO>" +
				getBaseAttackBonus() + "<PERSONINFO>" + getBaseAttackDice() + "<PERSONINFO>" + getNaturalDamageResistance() + "<PERSONINFO>" + 
				activeWeaponIndex + "<PERSONINFO>" + activeArmorIndex + "<PERSONINFO>";
		for (int i = 0; i < inventory.size(); i++) {
			result += "\n<ITEM>" + inventory.get(i).getSave();
		}
		return result;
	}
	
	
	/** Returns the translated name */
	public String getName(){
		return LanguagePack.get(name);
	}
	
	/** Returns the translated name */
	public String getNaturalWeapon(){
		return LanguagePack.get(naturalWeapon);
	}

	public int getLevel() {
		return level;
	}

	public int getMaxHP() {
		return maxHP;
	}

	public int getMaxMP() {
		return maxMP;
	}

	public int getCurrentHP() {
		return currentHP;
	}

	public int getCurrentMP() {
		return currentMP;
	}

	public int getPosX() {
		return posX;
	}

	public int getPosY() {
		return posY;
	}

	public boolean isFriendly() {
		return friendly;
	}

	public int getNaturalDamageResistance() {
		return naturalDamageResistance;
	}

	public Weapon getActiveWeapon() {
		return activeWeapon;
	}

	public Armor getActiveArmor() {
		return activeArmor;
	}

	public Map getMap() {
		return map;
	}
	
	public void setCurrentHP(int currentHP) {
		this.currentHP = currentHP;
	}

	public void setPosX(int posX) {
		this.posX = posX;
	}

	public void setPosY(int posY) {
		this.posY = posY;
	}

	public void setFriendly(boolean friendly) {
		this.friendly = friendly;
	}

	public void setLevel(int level) {
		this.level = level;
	}

	public void setMaxHP(int maxHP) {
		this.maxHP = maxHP;
	}

	public void setMaxMP(int maxMP) {
		this.maxMP = maxMP;
	}

	public void setCurrentMP(int currentMP) {
		this.currentMP = currentMP;
	}

	public void setNaturalWeapon(String naturalWeapon) {
		this.naturalWeapon = naturalWeapon;
	}

	public int getBaseAttackDice() {
		return baseAttackDice;
	}

	public void setBaseAttackDice(int baseAttackDice) {
		this.baseAttackDice = baseAttackDice;
	}

	public int getBaseAttackBonus() {
		return baseAttackBonus;
	}

	public void setBaseAttackBonus(int baseAttackBonus) {
		this.baseAttackBonus = baseAttackBonus;
	}

	public void setNaturalDamageResistance(int naturalDamageResistance) {
		this.naturalDamageResistance = naturalDamageResistance;
	}

	public void setActiveArmor(Armor activeArmor) {
		this.activeArmor = activeArmor;
	}

	public void setActiveWeapon(Weapon activeWeapon) {
		this.activeWeapon = activeWeapon;
	}

	public Inventory getInventory() {
		return inventory;
	}
	
	public void setMap(Map map) {
		this.map = map;
	}
}